Rust MaybeUninit
Union MaybeUninit is a wrapper type to construct uninitialized instances of T
.
pub union MaybeUninit {
uninit: (),
value: ManuallyDrop,
}
MaybeUninit
is providing a safe, controlled way to work with uninitialized memory.
Like the [Point: 10]
in the following example, it is redundant to initialize all the Point
s to 0.0
at the beginning of the function create_points_with_init
, we may want to avoid the redundant initialization, and set each point one-by-one directly.
use std::mem::MaybeUninit;
#[derive(Debug, Clone, Copy)]
struct Point {
x: f32,
y: f32,
z: f32,
}
fn create_points_with_init() -> [Point; 10] {
// the redundant initialization
let mut points: [Point; 10] = [Point {
x: 0.0,
y: 0.0,
z: 0.0,
}; 10];
for (i, point) in points.iter_mut().enumerate() {
point.x = i as f32;
point.y = (i * 2) as f32;
point.z = (i * 3) as f32;
}
points
}
We can simply construct an uninitialized instance of [Piont; 10]
: MaybeUninit<[Point; 10]>
to skip the initialization.
fn create_points_without_init() -> [Point; 10] {
let mut points: MaybeUninit<[Point; 10]> = MaybeUninit::uninit();
let ptr = points.as_mut_ptr() as *mut Point;
for i in 0..10 {
unsafe {
ptr.add(i).write(Point {
x: i as f32,
y: (i * 2) as f32,
z: (i * 3) as f32,
});
}
}
unsafe { points.assume_init() }
}
fn main() {
println!("create points with init");
let points = create_points_without_init();
for point in points {
println!("{point:?}");
}
println!("");
println!("create points without init");
let points = create_points_with_init();
for point in points {
println!("{point:?}");
}
}
Undefined Behavior
It will cause UB (undefined behavior) when trying to extract value from uninitialized container.
fn main() {
let nums: [usize; 10] = unsafe { MaybeUninit::uninit().assume_init() };
println!("{nums:?}");
}
cargo run
can be executed normally, but the values of nums
cannot be determined.
And we can use miri
to detect UB
❯ cargo +nightly miri run
error: Undefined Behavior: constructing invalid value at .value[0]: encountered uninitialized memory, but expected an integer
--> src/main.rs:58:38
|
58 | let nums: [usize; 10] = unsafe { MaybeUninit::uninit().assume_init() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .value[0]: encountered uninitialized memory, but expected an integer
The right way is let the compiler know each item in the array is uninitialized.
let nums: [MaybeUninit; 10] = unsafe { MaybeUninit::uninit().assume_init() };
Drop
Note that dropping a MaybeUninit
will never call T
’s drop code.
Take an example below, it's OK not to call assume_init_drop
for num: MaybeUninit
and point: MaybeUninit
. But we must call it for string: MaybeUninit
, otherwise it will cause error "memory leaked". As String
implement trait Drop
, and it need to call drop
function to clean the resource on the heap that it owns.
let mut num: MaybeUninit = MaybeUninit::uninit();
unsafe { num.assume_init_drop() };
num.write(100);
// unsafe { num.assume_init_drop() };
let mut point: MaybeUninit = MaybeUninit::uninit();
point.write(Point {
x: 0.0,
y: 0.1,
z: 0.2,
});
// unsafe { point.assume_init_drop() };
let mut string: MaybeUninit = MaybeUninit::uninit();
string.write("string".into());
// error: memory leaked, if not call String's drop
// unsafe { string.assume_init() };
// unsafe { string.assume_init_drop() };
Copy
types never need assume_init_drop()
because they have no destructor logic. Copy
types are prohibited from implementing Drop
(language guarantee)